﻿using Lucene.Net.Analysis;
using Lucene.Net.Analysis.PanGu;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using PanGu;
using PanGu.HighLight;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Tunynet.Utilities;
namespace Tunynet.Search
{
    /// <summary>
    /// 
    /// </summary>
    public class SearchEngine : ISearchEngine
    {
        private Analyzer a1;
        private int b1;
        private string c1;
        private bool d1;
        private static Highlighter e1 = null;
        private object f1 = new object();
        private static readonly object g1 = new object();
        private static ConcurrentDictionary<string, IndexWriter> h1 = new ConcurrentDictionary<string, IndexWriter>();
        private static ConcurrentDictionary<string, SearcherManager> i1 = new ConcurrentDictionary<string, SearcherManager>();
       /// <summary>
       /// 
       /// </summary>
        protected SearchEngine()
        {
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="indexPath"></param>
        public SearchEngine(string indexPath)
        {
            this.Initialize(indexPath, 10, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="indexPath"></param>
        /// <param name="mergeFactor"></param>
        /// <param name="analyzer"></param>
        public SearchEngine(string indexPath, int mergeFactor, Analyzer analyzer)
        {
            this.Initialize(indexPath, mergeFactor, analyzer);
        }
        public void Initialize(string indexPath, int mergeFactor = 10, Analyzer analyzer = null)
        {
            if (!this.d1)
            {
                lock (SearchEngine.g1)
                {
                    if (!this.d1)
                    {
                        this.c1 = WebUtility.GetPhysicalFilePath(indexPath);
                        if (mergeFactor < 10)
                        {
                            mergeFactor = 10;
                        }
                        this.b1 = mergeFactor;
                        if (analyzer == null)
                        {
                            analyzer = new PanGuAnalyzer();
                        }
                        this.a1 = analyzer;
                        this.g();
                        this.d1 = true;
                    }
                }
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public double GetIndexSize()
        {
            double num = 0.0;
            if (this.a())
            {
                FileInfo[] files = new DirectoryInfo(this.c1).GetFiles("*.*");
                if (files != null && files.Length > 0)
                {
                    FileInfo[] array = files;
                    for (int i = 0; i < array.Length; i++)
                    {
                        FileInfo fileInfo = array[i];
                        num += (double)fileInfo.Length;
                    }
                }
            }
            return num;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public DateTime GetLastModified()
        {
            DateTime dateTime = DateTime.MinValue;
            if (this.a())
            {
                FileInfo[] files = new DirectoryInfo(this.c1).GetFiles("*.*");
                if (files != null && files.Length > 0)
                {
                    FileInfo[] array = files;
                    for (int i = 0; i < array.Length; i++)
                    {
                        FileInfo fileInfo = array[i];
                        if (fileInfo.LastWriteTime > dateTime)
                        {
                            dateTime = fileInfo.LastWriteTime;
                        }
                    }
                }
            }
            return dateTime;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="indexDocuments"></param>
        /// <param name="isBeginning"></param>
        /// <param name="isEndding"></param>
        public void RebuildIndex(IEnumerable<Document> indexDocuments, bool isBeginning, bool isEndding)
        {
            lock (this.f1)
            {
                string text = Path.Combine(this.c1, "ReIndex");
                IndexWriter indexWriter = null;
                if (isBeginning)
                {
                    if (System.IO.Directory.Exists(text))
                    {
                        string[] files = System.IO.Directory.GetFiles(text);
                        string[] array = files;
                        for (int i = 0; i < array.Length; i++)
                        {
                            string path = array[i];
                            File.Delete(path);
                        }
                    }
                    else
                    {
                        System.IO.Directory.CreateDirectory(text);
                    }
                    Lucene.Net.Store.Directory directory = FSDirectory.Open(new DirectoryInfo(text));
                    indexWriter = new IndexWriter(directory, this.a1, true, IndexWriter.MaxFieldLength.UNLIMITED);
                    SearchEngine.h1[text] = indexWriter;
                }
                else
                {
                    indexWriter = this.c(text);
                }
                if (indexDocuments != null && indexDocuments.Count<Document>() > 0)
                {
                    try
                    {
                        foreach (Document current in indexDocuments)
                        {
                            indexWriter.AddDocument(current);
                        }
                    }
                    catch (Exception innerException)
                    {
                        throw new ExceptionFacade(string.Format("An unexpected error occured while add documents to the index [{0}].", this.c1), innerException);
                    }
                }
                if (isEndding)
                {
                    indexWriter.Optimize();
                    this.b(text);
                    this.e();
                    this.c();
                    string[] files2 = System.IO.Directory.GetFiles(this.c1, "*.*", SearchOption.TopDirectoryOnly);
                    string[] array2 = files2;
                    for (int j = 0; j < array2.Length; j++)
                    {
                        string path2 = array2[j];
                        File.Delete(path2);
                    }
                    files2 = System.IO.Directory.GetFiles(text, "*.*", SearchOption.TopDirectoryOnly);
                    string[] array3 = files2;
                    for (int k = 0; k < array3.Length; k++)
                    {
                        string text2 = array3[k];
                        File.Move(text2, text2.Replace("\\ReIndex", string.Empty));
                    }
                    System.IO.Directory.Delete(text);
                    this.g();
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="indexDocument"></param>
        /// <param name="reopen"></param>
        public void Insert(Document indexDocument, bool reopen = true)
        {
            this.Insert(new Document[]
			{
				indexDocument
			}, reopen);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="indexDocuments"></param>
        /// <param name="reopen"></param>
        public void Insert(IEnumerable<Document> indexDocuments, bool reopen = true)
        {
            lock (this.f1)
            {
                if (indexDocuments != null && indexDocuments.Count<Document>() != 0)
                {
                    IndexWriter indexWriter = this.d();
                    try
                    {
                        foreach (Document current in indexDocuments)
                        {
                            indexWriter.AddDocument(current);
                        }
                    }
                    catch (Exception innerException)
                    {
                        throw new ExceptionFacade(string.Format("An unexpected error occured while add documents to the index [{0}].", this.c1), innerException);
                    }
                    if (reopen)
                    {
                        this.b();
                    }
                }
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <param name="fieldNameOfId"></param>
        /// <param name="reopen"></param>
        public void Delete(string id, string fieldNameOfId, bool reopen = true)
        {
            this.Delete(new string[]
			{
				id
			}, fieldNameOfId, reopen);
        }
        public void Delete(IEnumerable<string> ids, string fieldNameOfId, bool reopen = true)
        {
            lock (this.f1)
            {
                if (ids != null || ids.Count<string>() != 0)
                {
                    IndexWriter indexWriter = this.d();
                    try
                    {
                        List<Term> list = new List<Term>();
                        foreach (string current in ids)
                        {
                            Term item = new Term(fieldNameOfId, current);
                            list.Add(item);
                        }
                        indexWriter.DeleteDocuments(list.ToArray());
                    }
                    catch (Exception innerException)
                    {
                        throw new ExceptionFacade(string.Format("An unexpected error occured while delete documents to the index [{0}].", this.c1), innerException);
                    }
                    if (reopen)
                    {
                        this.b();
                    }
                }
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="indexDocument"></param>
        /// <param name="id"></param>
        /// <param name="fieldNameOfId"></param>
        /// <param name="reopen"></param>
        public void Update(Document indexDocument, string id, string fieldNameOfId, bool reopen = true)
        {
            this.Update(new Document[]
			{
				indexDocument
			}, new string[]
			{
				id
			}, fieldNameOfId, reopen);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="indexDocuments"></param>
        /// <param name="ids"></param>
        /// <param name="fieldNameOfId"></param>
        /// <param name="reopen"></param>
        public void Update(IEnumerable<Document> indexDocuments, IEnumerable<string> ids, string fieldNameOfId, bool reopen = true)
        {
            lock (this.f1)
            {
                if (indexDocuments != null && indexDocuments.Count<Document>() != 0 && ids != null && ids.Count<string>() != 0)
                {
                    try
                    {
                        this.Delete(ids, fieldNameOfId, false);
                    }
                    catch (Exception innerException)
                    {
                        throw new ExceptionFacade(string.Format("An unexpected error occured while delete documents to the index [{0}].", this.c1), innerException);
                    }
                    try
                    {
                        this.Insert(indexDocuments, false);
                    }
                    catch (Exception innerException2)
                    {
                        throw new ExceptionFacade(string.Format("An unexpected error occured while add documents to the index [{0}].", this.c1), innerException2);
                    }
                    if (reopen)
                    {
                        this.b();
                    }
                }
            }
        }
        public void Commit()
        {
            lock (this.f1)
            {
                IndexWriter indexWriter = this.d();
                indexWriter.Commit();
            }
        }
        public void Close()
        {
            lock (this.f1)
            {
                this.c();
            }
        }
        public void Optimize()
        {
            lock (this.f1)
            {
                IndexWriter indexWriter = this.d();
                indexWriter.Optimize();
            }
        }
        public PagingDataSet<Document> Search(Query searchQuery, Filter filter, Sort sort, int pageIndex, int pageSize)
        {
            if (!this.a(this.c1))
            {
                return new PagingDataSet<Document>(Enumerable.Empty<Document>());
            }
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            if (sort == null)
            {
                sort = Sort.RELEVANCE;
            }
            TopFieldCollector topFieldCollector = TopFieldCollector.Create(sort, pageIndex * pageSize, false, true, false, true);
            SearcherManager searcherManager = this.f();
            IndexSearcher searcher = searcherManager.GetSearcher();
            PagingDataSet<Document> result;
            try
            {
                searcher.Search(searchQuery, filter, topFieldCollector);
                IEnumerable<Document> entities =
                    from scoreDoc in topFieldCollector.TopDocs().ScoreDocs.Skip((pageIndex - 1) * pageSize)
                    select searcher.Doc(scoreDoc.Doc);
                stopwatch.Stop();
                PagingDataSet<Document> pagingDataSet = new PagingDataSet<Document>(entities)
                {
                    TotalRecords = (long)topFieldCollector.GetTotalHits(),
                    PageSize = pageSize,
                    PageIndex = pageIndex,
                    QueryDuration = (double)stopwatch.ElapsedMilliseconds / 1000.0
                };
                result = pagingDataSet;
            }
            catch (Exception innerException)
            {
                throw new ExceptionFacade(string.Format("Index file maybe not exist under path: [{0}].", this.c1), innerException);
            }
            finally
            {
                searcherManager.ReleaseSearcher(searcher);
            }
            return result;
        }
        public IEnumerable<Document> Search(Query searchQuery, Filter filter, Sort sort, int topNumber)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            if (sort == null)
            {
                sort = Sort.RELEVANCE;
            }
            SearcherManager searcherManager = this.f();
            IndexSearcher searcher = searcherManager.GetSearcher();
            IEnumerable<Document> result;
            try
            {
                TopFieldDocs topFieldDocs = searcher.Search(searchQuery, filter, topNumber, sort);
                IEnumerable<Document> enumerable =
                    from scoreDoc in topFieldDocs.ScoreDocs
                    select searcher.Doc(scoreDoc.Doc);
                result = enumerable;
            }
            catch (Exception innerException)
            {
                throw new ExceptionFacade(string.Format("Index file maybe not exist under path: [{0}].", this.c1), innerException);
            }
            finally
            {
                searcherManager.ReleaseSearcher(searcher);
            }
            return result;
        }
        public static string Highlight(string keyWord, string content, int maxLengthOfResult)
        {
            if (SearchEngine.e1 == null)
            {
                SearchEngine.e1 = new Highlighter(new SimpleHTMLFormatter("<em class='tn-text-bright'>", "</em>"), new Segment());
                SearchEngine.e1.FragmentSize = maxLengthOfResult;
            }
            int num = 1;
            string text = null;
            if (!string.IsNullOrEmpty(content) && content.Length > num)
            {
                text = SearchEngine.e1.GetBestFragment(keyWord, content);
            }
            if (!string.IsNullOrEmpty(text))
            {
                return text;
            }
            return StringUtility.Trim(content, maxLengthOfResult);
        }
        private void g()
        {
            IndexWriter indexWriter = this.d();
            if (indexWriter != null)
            {
                return;
            }
            DirectoryInfo directoryInfo = new DirectoryInfo(this.c1);
            if (!directoryInfo.Exists)
            {
                directoryInfo.Create();
            }
            Lucene.Net.Store.Directory directory = FSDirectory.Open(new DirectoryInfo(this.c1));
            if (IndexReader.IndexExists(FSDirectory.Open(directoryInfo)))
            {
                indexWriter = new IndexWriter(directory, this.a1, false, IndexWriter.MaxFieldLength.UNLIMITED);
            }
            else
            {
                indexWriter = new IndexWriter(directory, this.a1, true, IndexWriter.MaxFieldLength.UNLIMITED);
            }
            indexWriter.SetMergeFactor(this.b1);
            SearchEngine.h1[this.c1] = indexWriter;
            SearcherManager value = new SearcherManager(indexWriter);
            SearchEngine.i1[this.c1] = value;
            this.d1 = true;
        }
        private SearcherManager f()
        {
            return this.e(this.c1);
        }
        private SearcherManager e(string A_0)
        {
            if (SearchEngine.i1.ContainsKey(A_0))
            {
                return SearchEngine.i1[A_0];
            }
            return null;
        }
        private void e()
        {
            this.d(this.c1);
        }
        private void d(string A_0)
        {
            if (SearchEngine.i1.ContainsKey(A_0))
            {
                SearcherManager searcherManager = null;
                SearchEngine.i1.TryRemove(A_0, out searcherManager);
                if (searcherManager != null)
                {
                    searcherManager.Close();
                }
            }
        }
        private IndexWriter d()
        {
            return this.c(this.c1);
        }
        private IndexWriter c(string A_0)
        {
            if (SearchEngine.h1.ContainsKey(A_0))
            {
                return SearchEngine.h1[A_0];
            }
            return null;
        }
        private void c()
        {
            this.b(this.c1);
        }
        private void b(string A_0)
        {
            if (SearchEngine.h1.ContainsKey(A_0))
            {
                IndexWriter indexWriter = null;
                SearchEngine.h1.TryRemove(A_0, out indexWriter);
                if (indexWriter != null)
                {
                    indexWriter.Close();
                    indexWriter.Dispose();
                    indexWriter = null;
                }
            }
        }
        private void b()
        {
            SearcherManager searcherManager = this.f();
            searcherManager.Reopen();
        }
        private bool a()
        {
            return this.a(this.c1);
        }
        private bool a(string A_0)
        {
            return IndexReader.IndexExists(FSDirectory.Open(new DirectoryInfo(A_0)));
        }
    }
}
